package com.hex.ds.hdtp.core.app.common.util;

import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.ap.shaded.freemarker.template.utility.StringUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class JaveShellUtil {

    public static final String SHELL_RESULT = "SHELL_RESULT";

    public static final String SHELL_ERR_RESULT = "SHELL_ERR_RESULT";

    public static final String SHELL_PID = "SHELL_PID";

    public static final String INVOKE_STATUS = "INVOKE_STATUS";

    public static final String SHELL_NORMAL_RESULT = "SHELL_NORMAL_RESULT";
    // SSH默认端口号
    @Value("${servers.host.remotePort}")
    int remotePort;
    @Value("${servers.host.remoteHost}")
    String remoteHost;
    @Value("${servers.host.username}")
    String username;
    @Value("${servers.host.password}")
    String password;
    @Value("${servers.host.userRemotePath}")
    String userRemotePath;
    @Value("${servers.flinkHome}")
    String flinkHome;

    @Value("${pyservers.host.remoteHost}")
    String pyremoteHost;
    @Value("${pyservers.host.username}")
    String pyusername;
    @Value("${pyservers.host.password}")
    String pypassword;
    @Value("${pyservers.host.userRemotePath}")
    String pyuserRemotePath;
    @Value("${pyservers.pycommond}")
    String pycommond;

    /**
     * 执行shell脚本
     *
     * @param shellPath 脚本文件路径 可以加参数，多个参数用空格隔开
     * @return
     */
    public static Map ExecCommand(String shellPath) {
        HashMap retMap = new HashMap();
        try {
            log.info("开始执行【ExecCommand】command:" + shellPath);
            Process pid = Runtime.getRuntime().exec(shellPath);
            log.info("结束执行【ExecCommand】command:" + pid.toString());
            retMap.put(SHELL_PID, pid.toString());
            log.info("shell pid:" + pid.toString());
            StreamGobbler errorStream = new StreamGobbler(pid.getErrorStream(), "ERROR");
            errorStream.start();
            StreamGobbler inputStream = new StreamGobbler(pid.getInputStream(), "INFO");
            inputStream.start();
            int exitCode = pid.waitFor();
            log.info("返回码：" + exitCode);
            log.info("错误流：" + errorStream.getContent());
            log.info("正确流：" + inputStream.getContent());
            retMap.put(SHELL_RESULT, String.valueOf(exitCode));
            retMap.put(SHELL_ERR_RESULT, inputStream.getContent() + errorStream.getContent());
            retMap.put(SHELL_NORMAL_RESULT, inputStream.getContent());
            if (1 == exitCode && StringUtils.isBlank(errorStream.getContent())) {
                retMap.put(SHELL_ERR_RESULT, inputStream.getContent());
            }
        } catch (Exception | Error e) {
            log.error("执行shell文件出错，原因：" + e.getMessage());
            retMap.put(SHELL_RESULT, "1");
            retMap.put(SHELL_ERR_RESULT, "执行shell文件出错，异常原因：" + e.getMessage());
            Thread.currentThread().interrupt();
        }
        return retMap;
    }

    /**
     * @Method executeDataShell
     * @Param
     * @Param shellPath
     * @Return java.util.Map
     * @Description 数据迁移远程调用shell命令
     * @Author xin.zhao
     * @Date 2023/9/20 16:50
     * @Version V1.0
     */
    public Map executeDataShell(String shellPath) {
        HashMap retMap = new HashMap();
        String remoteFilePath = userRemotePath;
        String command = "{}/bin/sql-client.sh embedded -s yarn-session -f {}/{}";
        String command1 = "export HADOOP_CLASSPATH=`hadoop classpath`";

        String[] parts = shellPath.split(" ");
        // 提取三个部分
        String filePath = "";
        for (int i = 2; i < parts.length; i++) {
            filePath += parts[i] + " ";
        }
        filePath = filePath.trim();
        Path path = Paths.get(filePath);
        String fileName = path.getFileName().toString();
        String command2 = StrUtil.format(command, flinkHome,userRemotePath, fileName);

        try {
            Session session = getConnectSession(username, remoteHost, password, remotePort);
            log.info("开始传输" + filePath + "文件至远端" + remoteFilePath);
            //传文件
            Channel channel = putFileToConnect(session, filePath, remoteFilePath);
            command2 = command1 + ";" + command2;
            //执行 sql-client.sh
            String result = executeCommandReturn(command2, session);
            String errorMessage = getErrorMessage(result);
            String syntaxErrorMessage = getSyntaxErrorMessage(result);
            if(StringUtils.isBlank(result)){
                retMap.put(SHELL_ERR_RESULT, "检查目录或文件是否正确");
            }else {
//                retMap.put(SHELL_ERR_RESULT, StrUtil.isBlank(errorMessage) ? "0" : errorMessage);
                retMap.put(SHELL_ERR_RESULT, StrUtil.isBlank(syntaxErrorMessage) ? "0" : syntaxErrorMessage);
            }
            if ("0".equals(retMap.get(SHELL_ERR_RESULT))) {
                retMap.put(SHELL_RESULT, "0");
            } else retMap.put(SHELL_RESULT, "1");
            log.info(result);
            log.info("关闭通道和会话");
            channel.disconnect();
            session.disconnect();
        } catch (JSchException | SftpException | IOException e) {
            retMap.put(SHELL_RESULT, "1");
            retMap.put(SHELL_ERR_RESULT, "执行shell文件出错，异常原因：" + e.getMessage());
        }
        return retMap;
    }

    public Map executeScriptShell(String shellPath) {
        HashMap retMap = new HashMap();
        String[] parts = shellPath.split(" ", 3);
        String command = "{} {}/{} {}";
        try {
            //连接服务器
            Session session = getConnectSession(pyusername, pyremoteHost, pypassword, remotePort);
            //传输转换后的py文件
            String filePath = parts[1];
            log.info("开始传输" + filePath + "文件至远端:"+ pyremoteHost + "目录：" + pyuserRemotePath);

            Channel channel = putFileToConnect(session, filePath, pyuserRemotePath);
            //执行python命令
            //获取文件名
            filePath = filePath.trim();
            Path path = Paths.get(filePath);
            String fileName = path.getFileName().toString();
            //py命令
            String finallyCommand = StrUtil.format(command,pycommond, pyuserRemotePath, fileName, parts[2]);
            String result = executeCommandReturn(finallyCommand, session);
            //截取错误信息
            String errorMessage = getErrorMessage(result);
            //TODO 脚本测试正确与否临时修改
            boolean resultFlag = result.contains("INSERT OVERWRITE 出错") ? false : true;
            if (resultFlag) {
                retMap.put(SHELL_RESULT, "0");
            } else {
                retMap.put(SHELL_RESULT, "1");
                retMap.put(SHELL_ERR_RESULT, errorMessage);
            }
            log.info(result);
            log.info("关闭通道和会话");
            channel.disconnect();
            session.disconnect();
        } catch (JSchException | SftpException | IOException e) {
            retMap.put(SHELL_RESULT, "1");
            retMap.put(SHELL_ERR_RESULT, "执行py文件出错，异常原因：" + e.getMessage());
        }
        return retMap;
    }

    /**
     * @Method executeReady
     * @Param
     * @Param command
     * @Param session
     * @Return void
     * @Description 数据迁移的准备命令 无返回
     * @Author xin.zhao
     * @Date 2023/9/20 16:51
     * @Version V1.0
     */
    public void executeReady(String command, Session session) {
        // 创建SSH通道
        try {
            ChannelExec channel1 = (ChannelExec) session.openChannel("exec");
            log.info("执行" + command + "命令");
            channel1.setCommand(command);
            channel1.connect();
        } catch (JSchException e) {
            throw new RuntimeException(e);
        }
    }

    public String executeCommandReturn(String command, Session session) throws JSchException, IOException {
        // 创建SSH通道
        ChannelExec channel = (ChannelExec) session.openChannel("exec");
        // 设置执行的命令
        channel.setErrStream(System.err);
        log.info("执行" + command + "命令");
        channel.setCommand(command);
        channel.connect();
        // 读取标准输出 (stdout)
        InputStream stdout = channel.getInputStream();
        // 读取标准错误 (stderr)
        InputStream stderr = channel.getErrStream();
        byte[] buffer = new byte[1024];
        StringBuilder output = new StringBuilder();
        // 从stdout读取数据
        int bytesRead;
        while ((bytesRead = stdout.read(buffer)) != -1) {
            output.append(new String(buffer, 0, bytesRead));
        }
        // 从stderr读取数据
        while ((bytesRead = stderr.read(buffer)) != -1) {
            output.append(new String(buffer, 0, bytesRead));
        }
        return output.toString();
    }

    /**
     * @Method getErrorMessage
     * @Param
     * @Param message
     * @Return java.lang.String
     * @Description 数据迁移读取命令输出信息 截取错误信息
     * @Author xin.zhao
     * @Date 2023/9/20 16:50
     * @Version V1.0
     */
    public String getErrorMessage(String message) {
        String errorMessage = "";
        //String regex = "\\[ERROR\\] (.{1,60})";  // 捕获 "[ERROR]" 后的报错信息
        String regex = "\\[ERROR\\] (.+?)(?:\\n\\[ERROR\\]|$)";
        Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        Matcher matcher = pattern.matcher(message);
        if (matcher.find()) {
            errorMessage = matcher.group(1);
        }
        return errorMessage;
    }
    public String getSyntaxErrorMessage(String message) {
        String syntaxErrorMessage = "";
        String regex = "(?s)Syntax: (.+?)(?=\\n\\w+:|$)";   // 捕获 "Syntax" 后的报错信息
        Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        Matcher matcher = pattern.matcher(message);
        if (matcher.find()) {
            syntaxErrorMessage = matcher.group(1);
        }
        return syntaxErrorMessage;
    }

    /**
     * @Method getConnectSession
     * @Param
     * @Param remotePort
     * @Return com.jcraft.jsch.Session
     * @Description 获取服务器连接
     * @Author xin.zhao
     * @Date 2023/9/25 17:51
     * @Version V1.0
     */
    public Session getConnectSession(String username, String remoteHost, String password, int remotePort) throws JSchException {
        JSch jSch = new JSch();
        Session session = jSch.getSession(username, remoteHost, remotePort);
        session.setPassword(password);
        session.setConfig("StrictHostKeyChecking", "no"); // 忽略主机密钥检查
        session.connect(1500);
        return session;
    }

    /**
     * @Method putFileToConnect
     * @Param
     * @Param session
     * @Param filePath
     * @Return com.jcraft.jsch.Channel
     * @Description 上传文件
     * @Author xin.zhao
     * @Date 2023/9/26 9:13
     * @Version V1.0
     */
    public Channel putFileToConnect(Session session, String localPath, String putFilePath) throws JSchException, SftpException {
        //传文件
        ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
        channel.connect();
        checkPathExist(channel,putFilePath);
        channel.put(localPath, putFilePath);
        return channel;
    }

    /**
     * @Method checkPathExist <br>
     * @Param
     * @Param channel
     * @Param putFilePath <br>
     * @Return void <br>
     * @Description 检查目录是否存在，不存在则创建<br>
     * @Author jian.qiao<br>
     * @Date 2023/11/1 19:10 <br>
     * @Version V1.0<br>
     */
    public void checkPathExist(ChannelSftp channel,String putFilePath) throws SftpException {
        try {
            channel.ls(putFilePath);
        } catch (Exception e) {
            // 目录不存在，创建目录
            channel.mkdir(putFilePath);
        }
    }

    /**
     * @Method pullFileFromConnect
     * @param session
     * @param pullFilePath
     * @param localPath
     * @Return com.jcraft.jsch.Channel
     * @Description 获取文件
     * @Author Yin.Yang
     * @Date 2023/10/8 14:20
     * @Version V1.0
     */
    public Channel pullFileFromConnect(Session session, String pullFilePath, String localPath) throws JSchException, SftpException {
        // 获取文件
        ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
        channel.connect();
        channel.get(pullFilePath, localPath);
        return channel;
    }
}
